  class Polygon {
    constructor(canvasNode) {
      this.canvasNode = canvasNode;
      this.gl = getWebGLContext(canvasNode);
      this.data = [];
      this._webglScript();
      this.a_Position = this.gl.getAttribLocation(this.gl.program, 'a_Position');
      this.u_FragColor = this.gl.getUniformLocation(this.gl.program, 'u_FragColor');
      this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer());
      this.clearColor();
      this.gl.vertexAttribPointer(this.a_Position, 2, this.gl.FLOAT, false, 0, 0);
      this.gl.enableVertexAttribArray(this.a_Position);
    }

    _webglScript() {
      let VSHADER_SOURCE = ` 
      attribute vec4 a_Position;
      void main(){
        gl_Position = a_Position;
      }`;

      let FSHADER_SOURCE = `
      precision mediump float;
      uniform vec4 u_FragColor;
      void main(){
        gl_FragColor = u_FragColor;
      }`;
      if (!initShaders(this.gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.error('Failed to initialize shaders');
        return;
      }
    }

    clearColor(color = [0.0, 0.0, 0.0, 1.0]) {
      this.gl.clearColor(...color);
      this.gl.clear(this.gl.COLOR_BUFFER_BIT);
    }

    _getNetTable(array = []) {
      let netArray = new Map();
      for (let i = 0, length = array.length; i < length; i = i + 2) {
        let tempArray = [];
        let [x1, y1] = [array[(i + length - 2) % length], array[(i + length - 1) % length]];
        let [xm, ym] = [array[i], array[i + 1]];
        let [x2, y2] = [array[(i + length + 2) % length], array[(i + length + 3) % length]];
        if (y1 > ym) {
          let deltaX = xm === x1 ? 0 : ((x1 - xm) / (y1 - ym));
          let obj = {
            deltaX,
            yMax: y1,
            xMin: xm
          };
          tempArray.push(obj);
        }
        if (y2 > ym) {
          let deltaX = xm === x2 ? 0 : ((x2 - xm) / (y2 - ym));
          let obj = {
            deltaX,
            yMax: y2,
            xMin: xm
          };
          tempArray.push(obj);
        }
        if (tempArray.length < 1) continue;
        if (netArray.get(ym)) {
          netArray.set(ym, netArray.get(ym).concat(tempArray));
        } else {
          netArray.set(ym, tempArray);
        }
      }
      return netArray;
    }

    _getAETable(netTable, yMin, yMax) {
      if (!netTable) return;
      let aeTable = new Map();
      for (let i = yMin, length = yMax - yMin; i < yMax; i++) {
        // aeTable[i] = [];
        aeTable.set(i, []);
        if (netTable.get(i)) {
          let netArray = netTable.get(i);
          netArray.forEach(obj => {
            if (obj['yMax'] > i) {
              aeTable.get(i).push({
                deltaX: obj['deltaX'],
                yMax: obj['yMax'],
                x: obj['xMin']
              });
            }
          });
        }
        if (i > yMin) {
          aeTable.get(i - 1).forEach(obj => {
            if (obj['yMax'] > i) {
              aeTable.get(i).push({
                deltaX: obj['deltaX'],
                yMax: obj['yMax'],
                x: obj['x'] + obj['deltaX']
              });
            }
          })
        }
      }
      return aeTable;
    }

    getRandomData(pointNumForEachPolygon = 0) {
      // this.data = [];
      for (let j = 0; j < pointNumForEachPolygon * 2; j++) {
        if (Math.random() < 0.5) {
          this.data.push((-1) * Math.random());
        } else {
          this.data.push(Math.random());
        }
      }
    }

    getPointsData(data) {
      let [canvasWidth, canvasHeight] = [this.canvasNode.offsetWidth, this.canvasNode.offsetHeight];

      let [webglToPxX, webglToPxY] = [canvasWidth / 2, canvasHeight / 2];

      let [xMin, yMin, xMax, yMax] = [Math.round(data[0] * webglToPxX), Math.round(data[1] * webglToPxY), Math.round(data[0] * webglToPxX), Math.round(data[1] * webglToPxY)];

      let newDataForPX = [];
      for (let i = 0, length = data.length; i < length; i += 2) {
        let [x, y] = [Math.round(data[i] * webglToPxX), Math.round(data[i + 1] * webglToPxY)];
        newDataForPX.push(x, y);
        xMin = xMin > x ? x : xMin;
        yMin = yMin > y ? y : yMin;
        xMax = xMax < x ? x : xMax;
        yMax = yMax < y ? y : yMax;
      }

      let aETable = this._getAETable(this._getNetTable(newDataForPX), yMin, yMax);
      let tempKeys = aETable.keys();
      let keys = Array.from(tempKeys).sort();

      let pointsData = [];
      // for (let key of keys) {
      for (let i = 0, length = keys.length; i < length; i++) {
        let tempMap = [];
        aETable.get(keys[i]).forEach(value => {
          tempMap.push(value['x'] / webglToPxX);
        })
        tempMap.sort();
        for (let j of tempMap) {
          pointsData.push(j, keys[i] / webglToPxY);
        }
      }
      return pointsData;
    }

    render(pointsData, fillColor = [0.0, 0.1, 1.0, 1.0]) {
      if (!pointsData || pointsData === null) {
        pointsData = this.getPointsData(this.data) //
      }
      let gl = this.gl;
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pointsData), gl.DYNAMIC_DRAW);
      gl.uniform4f(this.u_FragColor, ...fillColor);
      gl.drawArrays(gl.LINES, 0, pointsData.length / 2);
    }

  }

  class PolygonByCanvas {
    constructor(canvasNode) {
      this.canvasNode = canvasNode;
      this.ctx = canvasNode.getContext('2d');
    }
    getRandomData() {
      let width = this.canvasNode.offsetWidth;
      let height = this.canvasNode.offsetHeight;
      return [Math.random() * width, Math.random() * height];
    }
    render(lineNum, pointsNum) {
      let ctx = this.ctx;
      ctx.clearRect(0, 0, this.canvasNode.width, this.canvasNode.height);
      ctx.fillStyle = 'rgb(255,0,25)';
      for (let i = 0; i < lineNum; i++) {
        ctx.beginPath();
        ctx.moveTo(...this.getRandomData());
        for (let j = 0; j < pointsNum - 1; j++) {
          ctx.lineTo(...this.getRandomData());
        }
        ctx.fill();
      }
    }
  }

  export {
    Polygon,
    PolygonByCanvas
  }